import { Change } from 'diff';
import { Context } from './context';
import { formatAndFitHunkLine } from './formatAndFitHunkLine';
import { FormattedString } from './formattedString';
import { HunkPart } from './iterFormatHunk';

/**
 * Formats a "unified diff" hunk i.e. a hunk from a traditional diff.
 */
export async function* iterFormatUnifiedDiffHunkUnified(
    context: Context,
    hunkParts: HunkPart[],
    lineChanges: (Change[] | null)[]
): AsyncIterable<FormattedString> {
    const lineWidth = context.SCREEN_WIDTH;

    const [
        { fileName: fileNameA, lines: hunkLinesA },
        { fileName: fileNameB, lines: hunkLinesB },
    ] = hunkParts;
    let [{ startLineNo: lineNoA }, { startLineNo: lineNoB }] = hunkParts;

    let indexA = 0,
        indexB = 0;
    while (indexA < hunkLinesA.length) {
        const hunkLineA = hunkLinesA[indexA];
        const prefixA = hunkLineA?.slice(0, 1) ?? null;

        switch (prefixA) {
            case null:
                // Ignore the missing lines we insert to match up indexes
                break;
            case '-':
                yield* formatAndFitHunkLine(
                    context,
                    lineWidth,
                    fileNameA,
                    lineNoA,
                    hunkLineA,
                    lineChanges[indexA]
                );
                lineNoA++;
                break;
            default:
                // indexA is pointing to an unmodified line, so yield all the
                // inserted lines from indexB up to this line
                while (indexB < indexA) {
                    const hunkLineB = hunkLinesB[indexB];
                    if (hunkLineB !== null) {
                        yield* formatAndFitHunkLine(
                            context,
                            lineWidth,
                            fileNameB,
                            lineNoB,
                            hunkLineB,
                            lineChanges[indexB]
                        );
                        lineNoB++;
                    }
                    indexB++;
                }

                // now yield the unmodified line, which should be present in both
                yield* formatAndFitHunkLine(
                    context,
                    lineWidth,
                    fileNameA,
                    lineNoA,
                    hunkLineA,
                    lineChanges[indexB]
                );
                lineNoA++;
                lineNoB++;
                indexB++;
        }

        indexA++;
    }

    // yield any remaining lines in hunk B, which can happen if there were more
    // insertions at the end of the hunk
    while (indexB < hunkLinesB.length) {
        const hunkLineB = hunkLinesB[indexB];
        if (hunkLineB !== null) {
            yield* formatAndFitHunkLine(
                context,
                lineWidth,
                fileNameB,
                lineNoB,
                hunkLineB,
                lineChanges[indexB]
            );
            lineNoB++;
        }
        indexB++;
    }
}

/**
 * Formats a "combined diff" hunk i.e. a hunk from a combined diff, generated by
 * --cc or --combined options, which are defaults for merge commits.
 */
export async function* iterFormatCombinedDiffHunkUnified(
    context: Context,
    hunkParts: HunkPart[],
    lineChanges: (Change[] | null)[]
): AsyncIterable<FormattedString> {
    const lineWidth = context.SCREEN_WIDTH;

    // The final hunk part shows the current state of the file, so we just
    // display that with additions and deletions highlighted.
    const { fileName, lines, startLineNo } = hunkParts[hunkParts.length - 1];
    let lineNo = startLineNo;
    let numDeletes = 0;

    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const prefix = line?.slice(0, 1) ?? null;
        if (prefix == '-') {
            numDeletes++;
        } else {
            lineNo -= numDeletes;
            numDeletes = 0;
        }
        yield* formatAndFitHunkLine(
            context,
            lineWidth,
            fileName,
            lineNo,
            line,
            lineChanges[i]
        );
        lineNo++;
    }
}
